package cn.donting.web.desktop.core.app;

import cn.donting.web.desktop.core.Application;
import cn.donting.web.desktop.core.dao.AppInfoRepository;
import cn.donting.web.desktop.core.domain.Wap;
import cn.donting.web.desktop.core.exception.AppException;
import cn.donting.web.desktop.core.exception.AppRuntimeException;
import cn.donting.web.desktop.core.listener.AppListener;
import cn.donting.web.desktop.core.loader.AppLoader;
import cn.donting.web.desktop.core.entity.AppInfo;
import cn.donting.web.desktop.core.evn.CoreProperties;
import cn.donting.web.desktop.top.App;
import cn.donting.web.desktop.top.AppType;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.*;
import java.util.function.Supplier;


/**
 * app 管理类
 */
@Slf4j
public class AppManager {

//    public static final String app_File_ExeNem="wap";

    @Autowired
    AppInfoRepository appInfoRepository;

    @Autowired
    DesktopManager desktopManager;

    @Autowired
    ApplicationContext applicationContext;

    /**
     * 数据空间
     */
    public static final String SYS_PATH = CoreProperties.getSpace() + File.separator + "sys";
    /**
     * 安装的app 目录
     */
    public static final String INSTALL_PATH = SYS_PATH + File.separator + "apps";
    /**
     * app 图标目录
     */
    public static final String APP_ICON = INSTALL_PATH + File.separator + "icon";

    static {
        File file = new File(SYS_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }
        file = new File(INSTALL_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }
        file = new File(APP_ICON);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    /**
     * 运行中的 App
     */
    private HashMap<Long, Application> runningApp = new HashMap<>();


    /**
     * 停止app
     *
     * @param id appId
     * @return code 退出码
     * @throws Exception    异常信息
     * @throws AppException app 异常信息
     */
    public synchronized int stopApp(Long id) throws Exception {
        Application application = runningApp.remove(id);
        Assert.notNull(application, (Supplier) () -> new AppException("appId:" + id + " 未运行"));
        int exitCode = application.stop();
        log.info(StrUtil.format("退出app id[{}] name:[{}]", application.getId(), application.getApp().name()));
        return exitCode;
    }

    /**
     * App 是否处于运行状态
     *
     * @param id appId
     * @return bool 是否运行
     */
    public boolean isRunning(Long id) {
        if (id == Application.sysAppId) {
            return true;
        }
        return runningApp.containsKey(id);
    }

    /**
     * 启动App
     *
     * @param id appId
     * @return Application
     * @throws AppRuntimeException app启动异常
     * @throws Exception           泛化异常
     */
    public synchronized Application startApp(Long id) throws Exception {
        //处于running 直接返回
        if (isRunning(id)) {
            return runningApp.get(id);
        }
        //获取appInfo
        AppInfo appInfo = appInfoRepository.findById(id).get();
        if (appInfo == null) {
            throw new AppRuntimeException("appId：" + id + " 未安装");
        }
        List<AppListener> appListeners = getAppListeners();
        for (AppListener appListener : appListeners) {
            appListener.beforeStart(appInfo);
        }
        //获取app的文件
        String fileName = appInfo.getFileName();
        File file = new File(INSTALL_PATH + File.separator + fileName);
        //获取加载器
        AppLoader appLoader = getAppLoader(file);
        Application app = appLoader.loader(file, appInfo);
        //运行
        app.run();
        runningApp.put(id, app);
        for (AppListener appListener : appListeners) {
            appListener.afterStart(app);
        }
        log.info("runing app:" + id + "[" + appInfo.getName() + "]");
        return app;
    }

    /**
     * App 安装
     *
     * @param file 安装File 文件
     * @return AppInfo
     * @throws Exception 泛化异常
     */
    public synchronized AppInfo install(File file) throws Exception {
        String installPath = INSTALL_PATH + File.separator + UUID.randomUUID().toString() + ".jar";
        return install(file, installPath);
    }

    /**
     * App 安装
     *
     * @param bytes 文件文件二进制
     * @return AppInfo
     * @throws Exception 泛化异常
     */
    public synchronized AppInfo install(byte[] bytes) throws Exception {
        //生成文件名
        String installPath = INSTALL_PATH + File.separator + UUID.randomUUID().toString() + ".jar";
        //创建并写入文件
        File file = new File(installPath);
        file.createNewFile();
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(bytes);
        fileOutputStream.close();
        try {
            //保存安装信息
            AppInfo appInfo = saveInstall(file);
            return appInfo;
        } catch (Exception e) {
            log.error("安装失败");
            log.error(e.getMessage(), e);
            //安装失败删除 app文件
            file.delete();
            throw e;
        }
    }


    /**
     * App 安装
     *
     * @param file        安装文件
     * @param installPath 安装的文件路径
     * @return AppInfo
     * @throws Exception 泛化异常
     */
    protected synchronized final AppInfo install(File file, String installPath) throws Exception {
        File installFile = new File(installPath);
        FileUtil.copy(file, installFile, false);
        AppInfo appInfo = saveInstall(installFile);
        return appInfo;

    }

    /**
     * 保存安装信息
     *
     * @param file file
     * @return AppInfo
     * @throws Exception 泛化异常
     */
    private synchronized AppInfo saveInstall(File file) throws Exception {
        //获取加载器
        AppLoader appLoader = getAppLoader(file);
        Application app = appLoader.loader(file, null);
        App appInfo = app.getApp();
        AppInfo appInfos = AppInfo.buildInApp(appInfo);
        app.setAppInfo(appInfos);
        String appKey = app.getAppKey();
        appInfos.setAppKey(appKey);
        AppInfo appInfoByAppKey = appInfoRepository.findAppInfoByAppKey(appKey);
        if (appInfoByAppKey != null) {
            file.delete();
            throw new AppException("该App已安装(如需更新请使用更新安装):" + appKey);
        }

        appInfos.setInstallTime(new Date());
        appInfos.setFileName(file.getName());
        //复制appICon
        URL resource = app.getAppClassLoader().getResource(appInfo.icon());
        if (resource == null) {
            resource = this.getClass().getClassLoader().getResource("icon/appDefaultIcon.png");
        }
        //复制appICon到目标未知
        String extName = FileUtil.extName(resource.getFile());
        File iconTarget = new File(APP_ICON + File.separator + UUID.randomUUID().toString() + "." + extName);
        iconTarget.createNewFile();
        cn.donting.web.desktop.core.unilt.FileUtil.copy(iconTarget, resource);

        //保存appInfos
        appInfos.setIcon(iconTarget.getName());
        appInfos = appInfoRepository.save(appInfos);

        //触发AppListener install
        Map<String, AppListener> appListenerMap = applicationContext.getBeansOfType(AppListener.class);
        for (Map.Entry<String, AppListener> appListenerEntry : appListenerMap.entrySet()) {
            AppListener value = appListenerEntry.getValue();
            value.install(appInfos);
        }
        checkInstallApp(appInfos);
        return appInfos;
    }

    protected synchronized final AppInfo update(File file) throws Exception {
        //获取加载器
        AppLoader appLoader = getAppLoader(file);
        Application app = appLoader.loader(file, null);
        App appInfo = app.getApp();
        String appKey = app.getAppKey();
        AppInfo appInfoByAppKey = appInfoRepository.findAppInfoByAppKey(appKey);
        if (appInfoByAppKey == null) {
            throw new AppException("该app未安装不能执行更新");
        }
        if (appInfoByAppKey.getNumberVersion() > appInfo.numberVersion()) {
            file.delete();
            String format = StrUtil.format("更新版本低于已安装版本:{}<={}", appInfo.numberVersion(), appInfoByAppKey.getNumberVersion());
            throw new AppException(format);
        }
        unInstall(appInfoByAppKey.getAppId());
        return install(file);
    }


    /**
     * 卸载App
     *
     * @param id appId
     */
    public synchronized void unInstall(Long id) {
        //停止running中的app
        if (runningApp.containsKey(id)) {
            try {
                stopApp(id);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

        AppInfo appInfo = appInfoRepository.findById(id).get();
        Map<String, AppListener> appListenerMap = applicationContext.getBeansOfType(AppListener.class);
        for (Map.Entry<String, AppListener> appListenerEntry : appListenerMap.entrySet()) {
            AppListener value = appListenerEntry.getValue();
            value.uninstall(appInfo);
        }

        //删除appICon
        String icon = appInfo.getIcon();
        File file = new File(APP_ICON + File.separator + icon);

        file.delete();
        //删除文件
        File appInstallFile = getAppInstallFile(appInfo);
        //删除db
        appInfoRepository.delete(appInfo);
        //删除安装文件.jar
        appInstallFile.delete();
    }

    /**
     * 根据文件获取能加载的加载器
     *
     * @param file app文件
     * @return AppLoader app加载器
     * @throws AppRuntimeException AppRuntimeException  未找到合适的加载器
     */
    protected AppLoader getAppLoader(File file) throws AppRuntimeException {
        //获取spring容器中的加载器
        Map<String, AppLoader> beansOfType = applicationContext.getBeansOfType(AppLoader.class);
        Set<Map.Entry<String, AppLoader>> entries = beansOfType.entrySet();
        for (Map.Entry<String, AppLoader> entry : entries) {
            if (entry.getValue().isLoader(file)) {
                return entry.getValue();
            }
        }
        throw new AppRuntimeException(file.getPath() + " 未找到合适的加载器！！！");
    }

    /**
     * 获取运行中的App
     *
     * @param id appId
     * @return Application
     */
    public Application getApp(Long id) {
        return runningApp.get(id);
    }

    /**
     * 获取 App 安装后的文件
     *
     * @param id appID
     * @return File
     */
    public File getAppInstallFile(Long id) {
        AppInfo appInfo = appInfoRepository.findById(id).get();
        return getAppInstallFile(appInfo);
    }

    /**
     * 获取 App 安装后的文件
     *
     * @param appInfo
     * @return File
     */
    public File getAppInstallFile(AppInfo appInfo) {
        return new File(INSTALL_PATH + File.separator + appInfo.getFileName());
    }

    /**
     * 检查 安装的App 是否符合默认设置
     *
     * @param app AppInfo
     */
    private void checkInstallApp(AppInfo app) {
        if (app.getType() == AppType.Desktop) {
            if (desktopManager.getWebDesktopAppId() == null) {
                log.info("设置默认桌面：" + app.getName());
                desktopManager.setWebDesktopId(app.getAppId() + "");
            }
            return;
        }
        if (app.getType() == AppType.FileResources) {
            if (desktopManager.getFileResourcesAppId() == null) {
                log.info("设置默认 文件资源器：" + app.getName());
                desktopManager.setFileResourcesId(app.getAppId() + "");
            }
            return;
        }
    }

    /**
     * 获取 App 安装后的文件
     *
     * @param appInfo
     * @return
     */
    public static File appFile(AppInfo appInfo) {
        File file = new File(INSTALL_PATH + File.separator + appInfo.getFileName());
        return file;
    }

    /**
     * 获取 app 的wap信息
     *
     * @param id appId
     * @return
     * @throws AppException app不存在
     */
    public Wap creatWap(Long id) throws AppException {
        AppInfo byAppId = appInfoRepository.findById(id).get();
        if (byAppId == null) {
            throw new AppException("不存在App[{" + id + "}]");
        }
        Wap wap = new Wap();
        wap.setArgs(new String[0]);
        wap.setId(id);
        return wap;
    }

    /**
     * 获取安装的所有的AppInfos
     *
     * @return
     */
    public List<AppInfo> getInstallApps() {
        List<AppInfo> all = appInfoRepository.findAll();
        return all;
    }

    /**
     * 获取安装后的 AppInfo
     *
     * @param id
     * @return
     */
    public AppInfo getAppInfo(Long id) {
        AppInfo byAppId = appInfoRepository.findById(id).get();
        return byAppId;
    }

    public List<AppListener> getAppListeners() {
        //触发AppListener install
        Map<String, AppListener> appListenerMap = applicationContext.getBeansOfType(AppListener.class);
        ArrayList<AppListener> appListeners = new ArrayList<>(appListenerMap.size());
        for (Map.Entry<String, AppListener> appListenerEntry : appListenerMap.entrySet()) {
            AppListener value = appListenerEntry.getValue();
            appListeners.add(value);
        }
        return appListeners;
    }


}
